// Magic Software, Inc.
// http://www.magic-software.com
// Copyright (c) 2000, All Rights Reserved
//
// Source code from Magic Software is supplied under the terms of a license
// agreement and may not be copied or disclosed except in accordance with the
// terms of that agreement.  The various license agreements may be found at
// the Magic Software web site.  This file is subject to the license
//
// RESTRICTED USE SOURCE CODE
// http://www.magic-software.com/License/restricted.pdf

#include "MgcBound.h"
#include "MgcMath.h"
#include "MgcMatrix3.h"

//---------------------------------------------------------------------------
MgcBound::MgcBound ()
    :
    m_kCenter(MgcVector3::ZERO)
{
    m_fRadius = 0.0;
}
//---------------------------------------------------------------------------
MgcBound& MgcBound::operator= (const MgcBound& rkBound)
{
    m_kCenter = rkBound.m_kCenter;
    m_fRadius = rkBound.m_fRadius;
    return *this;
}
//---------------------------------------------------------------------------
MgcBound& MgcBound::operator+= (const MgcBound& rkBound)
{
    static const MgcReal s_fTolerance = 1e-06;

    // difference of current sphere center and input sphere center
    MgcVector3 kDiff = m_kCenter - rkBound.m_kCenter;
    
    MgcReal fLengthSqr = kDiff.SquaredLength();
    MgcReal fDeltaRad = rkBound.m_fRadius - m_fRadius;
    MgcReal fDeltaRadSqr = fDeltaRad*fDeltaRad;
    MgcReal fLength, fAlpha;
    
    if ( fDeltaRad >= 0.0 )
    {
        if ( fDeltaRadSqr >= fLengthSqr )
        {
            // rkBound's sphere encloses 'this' sphere
            m_kCenter = rkBound.m_kCenter;
            m_fRadius = rkBound.m_fRadius;
        }
        else
        {
            // this' sphere does not enclose rkBound sphere
            fLength = MgcMath::Sqrt(fLengthSqr);
            if ( fLength > s_fTolerance )
            {
                fAlpha = (fLength - fDeltaRad)/(2.0*fLength);
                m_kCenter = rkBound.m_kCenter + fAlpha*kDiff;
            }
            m_fRadius = 0.5*(rkBound.m_fRadius+fLength+m_fRadius);
        }
    }
    else if ( fDeltaRadSqr < fLengthSqr )
    {
        // this' sphere does not enclose rkBound sphere
        fLength = MgcMath::Sqrt(fLengthSqr);
        if ( fLength > s_fTolerance )
        {
            fAlpha = (fLength - fDeltaRad)/(2.0*fLength);
            m_kCenter = rkBound.m_kCenter + fAlpha*kDiff;
        }
        m_fRadius = 0.5*(rkBound.m_fRadius+fLength+m_fRadius);
    }
    // else 'this' sphere encloses rkBound sphere

    return *this;
}
//---------------------------------------------------------------------------
MgcBound MgcBound::TransformBy (const MgcMatrix3& rkRotate,
    const MgcVector3& rkTranslate, MgcReal fScale) const
{
    MgcBound bound;
    bound.m_kCenter = fScale*(rkRotate*m_kCenter) + rkTranslate;
    bound.m_fRadius = fScale*m_fRadius;
    return bound;
}
//---------------------------------------------------------------------------
void MgcBound::ComputeFromData (unsigned int uiQuantity,
    const MgcVector3* akData)
{
    if ( uiQuantity == 0 )
    {
        m_kCenter = MgcVector3::ZERO;
        m_fRadius = 0.0;
        return;
    }

    // compute the axis-aligned box containing the data
    MgcVector3 kMin = akData[0], kMax = kMin;
    unsigned int uiI;
    for (uiI = 1; uiI < uiQuantity; uiI++) 
    {
        if ( kMin[0] > akData[uiI][0] )
            kMin[0] = akData[uiI][0];
        if ( kMin[1] > akData[uiI][1] )
            kMin[1] = akData[uiI][1];
        if ( kMin[2] > akData[uiI][2] )
            kMin[2] = akData[uiI][2];
        if ( kMax[0] < akData[uiI][0] )
            kMax[0] = akData[uiI][0];
        if ( kMax[1] < akData[uiI][1] )
            kMax[1] = akData[uiI][1];
        if ( kMax[2] < akData[uiI][2] )
            kMax[2] = akData[uiI][2];
    }

    // sphere center is the axis-aligned box center
    m_kCenter = 0.5*(kMin+kMax);

    // compute the radius
    MgcReal fRadiusSqr = 0.0;
    for (uiI = 0; uiI < uiQuantity; uiI++) 
    {
        MgcVector3 kDiff = akData[uiI] - m_kCenter;
        MgcReal fLengthSqr = kDiff.SquaredLength();
        if ( fLengthSqr > fRadiusSqr )
            fRadiusSqr = fLengthSqr;
    }
    m_fRadius = MgcMath::Sqrt(fRadiusSqr);
}
//---------------------------------------------------------------------------
MgcPlane::Side MgcBound::WhichSide (const MgcPlane& rkPlane) const
{
    MgcReal fPseudoDistance = rkPlane.DistanceTo(m_kCenter);

    if ( fPseudoDistance <= -m_fRadius)
    {
        return MgcPlane::NEGATIVE_SIDE;
    }
    else if ( fPseudoDistance >= m_fRadius)
    {
        return MgcPlane::POSITIVE_SIDE;
    }
    else
    {
        return MgcPlane::NO_SIDE;
    }
}
//---------------------------------------------------------------------------
